/*
 * Copyright  bocmbci
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import HashMap from '@ohos.util.HashMap';
import { CallbackContext } from './CallbackContext';
import { CordovaBridge } from './CordovaBridge';
import { CordovaInterface } from './CordovaInterface';
import { CordovaPlugin } from './CordovaPlugin';
import { CordovaWebViewService } from './CordovaWebViewService';
import { CoreAndroid } from './CoreAndroid';
import { PluginEntry } from './PluginEntry';
import { PluginResult, PluginResultStatus } from './PluginResult';

export class PluginManager{
  private static readonly TAG = "PluginManager";

  private SLOW_EXEC_WARNING_THRESHOLD: Number = 16;

  // List of service entries
  private pluginMap = new HashMap<string, CordovaPlugin>();
  private entryMap  = new HashMap<string, PluginEntry>();

  private cordovaInterface: CordovaInterface;
  private cordovaWebViewService: CordovaWebViewService;
  private isInitialized: boolean;

  private permissionRequester: CordovaPlugin;

  constructor(app: any, ctx: CordovaInterface, pluginEntries: Array<PluginEntry>) {
    this.cordovaInterface = ctx;
    this.cordovaWebViewService = app;

    //默认添加安卓插件
    let coreAndroidPlugin = new PluginEntry("CoreAndroid", CoreAndroid, null, false);
    this.addService(coreAndroidPlugin);

    this.setPluginEntries(pluginEntries);
  }

  public getPluginEntries(): Array<PluginEntry> {
    return Array.from(this.entryMap.values())
  }

  public setPluginEntries(pluginEntries: Array<PluginEntry>): void {
    if (this.isInitialized) {
      this.onPause(false);
      this.onDestroy();
      this.pluginMap.clear();
      this.entryMap.clear();
    }

    if(pluginEntries == null || pluginEntries == undefined){
      return;
    }

    for(let i = 0; i < pluginEntries.length; i ++){
      this.addService(pluginEntries[i])
    }

    if (this.isInitialized) {
      this.startupPlugins();
    }
  }

  init(){
    this.isInitialized = true;
    this.onPause(false);
    this.onDestroy();
    this.pluginMap.clear();
    this.startupPlugins();
  }

  startupPlugins(){
    for(const [key, value] of this.entryMap){
      if(value.isOnload()){
        this.getPlugin(value.getService());
      }
      else{
        this.pluginMap.set(value.getService(), null);
      }
    }

  }

  exec(service: string, action: string, callbackId: string, rawArgs: string, cordvoaBridge: CordovaBridge){
    const plugin = this.getPlugin(service);
    if (plugin === null) {
      console.log("exec() call to unknown plugin: " + service);
      const cr: PluginResult = new PluginResult(PluginResultStatus.CLASS_NOT_FOUND_EXCEPTION, "");
      this.cordovaWebViewService.sendPluginResult(cr, callbackId);
      return;
    }
    const callbackContext: CallbackContext = new CallbackContext(callbackId, cordvoaBridge);
    try {
      const pluginStartTime = Date.now();
      const wasValidAction = plugin.execute(action, rawArgs, callbackContext);
      const duration = Date.now() - pluginStartTime;

      if (duration / 1000 > this.SLOW_EXEC_WARNING_THRESHOLD) {
        console.warn("THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
      }
      if (!wasValidAction) {
        const cr: PluginResult = new PluginResult(PluginResultStatus.INVALID_ACTION, '');
        callbackContext.sendPluginResult(cr);
      }
    } catch (e: any) {
      console.error("Uncaught exception from plugin", e);
      callbackContext.error(e.message);
    }
  }

  getPlugin(service: string): CordovaPlugin{
    let ret = this.pluginMap.get(service);
    if(ret == null || ret == undefined){
      let pluginEntry = this.entryMap.get(service);
      if(pluginEntry == null){
        return null;
      }

      if(pluginEntry.getPlugin() != null){
        ret = pluginEntry.getPlugin();
      }
      else {
        ret = this.instantiatePlugin(pluginEntry.getPluginClass());
      }

      ret.privateInitialize(service, this.cordovaInterface, this.cordovaWebViewService);
      this.pluginMap.set(service, ret);
    }

    return ret;
  }

  addService(entry: PluginEntry){
    this.entryMap.set(entry.getService(), entry);
    if(entry.getPlugin() != null){
      entry.getPlugin().privateInitialize(entry.getService(), this.cordovaInterface, this.cordovaWebViewService)
      this.pluginMap.set(entry.getService(), entry.getPlugin());
    }
  }

  onPause(multitasking: boolean){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onPause(multitasking)
      }
    }
  }

  onResume(multitasking: boolean){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onResume(multitasking)
      }
    }
  }

  onStart(){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onStart()
      }
    }
  }

  onStop(){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onStop()
      }
    }
  }

  onDestroy(){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onDestroy()
      }
    }
  }

  onReceivedHttpAuthRequest(cordovaWebViewService: CordovaWebViewService,  handler, host: string, realm: string){

  }

  onReceivedClientCertRequest(cordovaWebViewService: CordovaWebViewService, request){

  }

  postMessage(id: string, data: Object){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onMessage(id, data);
      }
    }
    return this.cordovaInterface.onMessage(id, data);
  }

  shouldAllowRequest(url: string): boolean{
    for (const entry of this.entryMap.values()) {
      const plugin = this.pluginMap.get(entry.getService());
      if (plugin != null) {
        const result = plugin.shouldAllowRequest(url);
        if (result != null) {
          return result;
        }
      }
    }

    // Default policy:
    if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
      return true;
    }

    if (url.startsWith("file://")) {
      //This directory on WebKit/Blink based webviews contains SQLite databases!
      //DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
      return !url.includes("/app_webview/");
    }
    return false;
  }

  shouldAllowNavigation(url: string): boolean{
    for (const entry of this.entryMap.values()) {
      const plugin = this.pluginMap.get(entry.getService());
      if (plugin != null) {
        const result = plugin.shouldAllowNavigation(url);
        if (result != null) {
          return result;
        }
      }
    }

    // Default policy:
    return url.startsWith("file://") || url.startsWith("about:blank");
  }

  shouldAllowBridgeAccess(url: string): boolean{
    for (const entry of this.entryMap.values()) {
      const plugin = this.pluginMap.get(entry.getService());
      if (plugin != null) {
        const result = plugin.shouldAllowBridgeAccess(url);
        if (result != null) {
          return result;
        }
      }
    }

    // Default policy:
    // return url.startsWith("file://");
    return true;
  }

  shouldOpenExternalUrl(url: string): boolean{
    for (const entry of this.entryMap.values()) {
      const plugin = this.pluginMap.get(entry.getService());
      if (plugin != null) {
        const result = plugin.shouldOpenExternalUrl(url);
        if (result != null) {
          return result;
        }
      }
    }

    // Default policy:
    // External URLs are not allowed
    return false;
  }

  onOverrideUrlLoading(url: string): boolean{
    for (const entry of this.entryMap.values()) {
      const plugin = this.pluginMap.get(entry.getService());
      if (plugin != null && plugin.onOverrideUrlLoading(url)) {
        return true;
      }
    }
    return false;
  }

  onReset(){
    for(const [, plugin] of this.pluginMap){
      if(plugin != null){
        plugin.onReset();
      }
    }
  }

  instantiatePlugin(targetClass): CordovaPlugin{
    let ret: CordovaPlugin = Reflect.construct(targetClass, [])
    return ret;
  }
}